package com.hhf.rpc.core.fault.loadbalancer.impl;

import cn.hutool.core.collection.CollUtil;
import com.hhf.rpc.core.fault.loadbalancer.LoadBalancer;
import com.hhf.rpc.core.model.ServiceMetaInfo;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * @Author hhf
 * @Date 2024/10/20
 * @Description 一致性hash负载均衡
 */
@Slf4j
public class ConsistentHashLoadBalancer implements LoadBalancer {

    /**
     * 一致性hash环
     * 用于存放虚拟节点
     */
    private final TreeMap<Integer,ServiceMetaInfo> virtualNodes = new TreeMap<>();

    /**
     * 虚拟节点数量
     */
    private static final int VIRTUAL_NODE_NUM = 100;

    /**
     * 每次请求都需要重新构建虚拟节点环,保证能够及时的处理节点的变换
     * @param requestParams
     * @param serviceMetaInfoList
     * @return
     */
    @Override
    public ServiceMetaInfo select(Map<String, Object> requestParams, List<ServiceMetaInfo> serviceMetaInfoList) {
        if (CollUtil.isEmpty(serviceMetaInfoList)){
            return null;
        }
        int size = serviceMetaInfoList.size();
        if (size == 1){
            return serviceMetaInfoList.get(0);
        }

        //构建虚拟节点环
        for (ServiceMetaInfo serviceMetaInfo : serviceMetaInfoList){
            for (int i=0;i<VIRTUAL_NODE_NUM;i++){
                int hash = getHash(serviceMetaInfo.getServiceAddress() + "#" + i);
                virtualNodes.put(hash,serviceMetaInfo);
            }
        }

        //获取调用请求的hash
        int hash = getHash(requestParams);

        //选择最接近且大于等于调用请求的hash的虚拟节点
        Map.Entry<Integer, ServiceMetaInfo> ceilingEntry = virtualNodes.ceilingEntry(hash);
        if (ceilingEntry == null){
            ServiceMetaInfo serviceMetaInfo = virtualNodes.firstEntry().getValue();
            log.info("负载均衡选择服务:{}",serviceMetaInfo);
            return serviceMetaInfo;
        }
        ServiceMetaInfo serviceMetaInfo = ceilingEntry.getValue();
        log.info("负载均衡选择服务:{}",serviceMetaInfo);
        return serviceMetaInfo;
    }

    /**
     * Hash算法
     * @param object
     * @return
     */
    private int getHash(Object object){
        return object.hashCode();
    }
}
